from datetime import datetime as datetime_
from conf.constant import IndicatorDescribe

import numpy as np
import pandas as pd

from object.unit import Engine, Data, Order
from object.single.factor import FrontFactor
from conf.enum import TacticType, OrderDir
from conf.conf import prepare_num, push_process_num, max_pos, ori_value_val, mk_ori_val
from noflip_web.settings import BASE_DIR
import os

from tools.methods import get_sample, save_var_json
from tools.operate import read_csv
from operate.models import MkData, SgTactic
from tools.methods import safe_to_csv


class SingleData(Data):
    def __init__(self, variable_value):
        super().__init__(variable_value)

    def gen_com_data(self):
        """根据数据路径生成全量数据"""
        self.path = os.path.join(BASE_DIR, "mk_data", "single", self.name)
        self.com_data = read_csv(self.path)
        # --------------------------------------------------------
        # 对com_data进行变换至初始值为100，其涨跌幅保持不变
        # --------------------------------------------------------
        close = self.com_data["close"].values
        # 缩放因子
        scale_factor = mk_ori_val / close[0]

        # 提取 open、high、low、close 列
        price_data = self.com_data[['open', 'high', 'low', 'close']]

        # # 计算新的 open 第一个值缩放后的值
        # new_open_first_value = mk_ori_val

        # 进行缩放
        scaled_price_data = price_data * scale_factor
        scaled_price_data = scaled_price_data.round(2)

        # # 确保 open 的第一个值为 100
        # scaled_price_data = scaled_price_data * (new_open_first_value / scaled_price_data['open'][0])

        # 将缩放后的数据添加到原始 DataFrame 中，新列名与原来一致
        scaled_columns = [col for col in price_data.columns]
        self.com_data[scaled_columns] = scaled_price_data
        pass

    def gen_factor_data(self):
        """生成全量因子数据"""
        # 初始化因子类
        self.factor_ins = FrontFactor()
        # 关联data_ins的全量数据com_data，并生成因子数据
        self.factor_ins.link_com_data(self.com_data)
        # 根据variable_value中记录的tactics_factor_ls生成对应因子数据
        tactics_factor_ls = self.variable_value["lab_var_part2_4"]["tactics_factor_ls"]
        for factor_o in tactics_factor_ls:
            # 如果要计算的因子已经存在于数据中
            if factor_o in self.com_data.columns:
                continue

            if hasattr(self.factor_ins, factor_o):  # 检查实例是否有该函数
                func = getattr(self.factor_ins, factor_o)
                func()  # 调用函数并将结果放置com_data
            else:
                print(f"Function {factor_o} does not exist.")
                raise ValueError(f"{factor_o}没有在因子类中进行实现。")
        # 将com_data中含nan的行删除
        self.com_data = self.com_data.dropna()

        # 临时缓存下
        path = os.path.join(BASE_DIR, "data", "cache", "com_data.csv")
        safe_to_csv(self.com_data, path)

    def gen_multi_data(self, num: int):
        """自动获取最合适的进程数并分割为相应份数的数据，idx为索引，每一组idx从0开始"""
        self.group_cont = []  # 先对其初始化，避免重复累计
        # 生成分割后的数据，并将分割后的多个DataFrame数据拆分为多组line数据供single_backtest中的战法实例使用
        df_split = np.array_split(self.com_data, num)
        for i, part in enumerate(df_split):
            self.group_cont.append(part)


class SingleBacktest:
    def __init__(self, data, idx_addend, variable_value, queue):
        """接收被分割的全量行情数据，并根据战法生成对应订单信息"""
        self.data = data
        self.idx_addend = idx_addend
        self.variable_value = variable_value
        self.com_order_ls = []  # 其中元素为Order实例
        self.idx = 0

        # 结构变量
        self.queue = queue
        self.filter_tactic_cont = []
        self.open_tactic_cont = []
        self.close_tactic_cont = []

        # 基础因子
        self.open: np.ndarray = None
        self.high: np.ndarray = None
        self.low: np.ndarray = None
        self.close: np.ndarray = None
        self.volume: np.ndarray = None

        # 回测过程变量
        self.pos_ls = []
        self.value_ls = []
        self.now_pos = None

    def set_factor(self):
        """为战法关联所需的factor"""
        # 根据data生成以因子名为属性名的类属性，每个列作为一个factor
        tactics_factor_ls = self.variable_value["lab_var_part2_4"]["tactics_factor_ls"]
        for factor in tactics_factor_ls:
            setattr(self, factor, self.data[factor].to_numpy())
        # 对基础因子赋值
        self.open: np.ndarray = self.data["open"].values
        self.high: np.ndarray = self.data["high"].values
        self.low: np.ndarray = self.data["low"].values
        self.close: np.ndarray = self.data["close"].values
        self.volume: np.ndarray = self.data["volume"].values

    def set_tactic_ins(self):
        """将engine中提名的战法的实例加入到此进程的Backtest类"""
        # 导入模块
        from object.single import tactic
        # 实例化过滤战法实例
        filter_tactic_name_ls = self.variable_value["lab_var_part2_3"]["filter_ls"]
        for filter_tactic_o in filter_tactic_name_ls:
            # 获取战法的类名
            sg_tactic = SgTactic.objects.get(name=filter_tactic_o)
            tactic_class_name = sg_tactic.class_name
            # 实例化并关联bt_task
            cls = getattr(tactic, tactic_class_name)
            tactic_ins = cls()
            tactic_ins.bt_task = self
            # 加入相应容器
            self.filter_tactic_cont.append(tactic_ins)
            pass

        # 实例化开仓战法实例
        open_tactic_name_ls = self.variable_value["lab_var_part2_3"]["open_ls"]
        for open_tactic_o in open_tactic_name_ls:
            # 获取战法的类名
            sg_tactic = SgTactic.objects.get(name=open_tactic_o)
            tactic_class_name = sg_tactic.class_name
            # 实例化并关联bt_task
            cls = getattr(tactic, tactic_class_name)
            tactic_ins = cls()
            tactic_ins.bt_task = self
            # 加入相应容器
            self.open_tactic_cont.append(tactic_ins)
            pass

        # 实例化平仓战法实例
        close_tactic_name_ls = self.variable_value["lab_var_part2_3"]["close_ls"]
        for close_tactic_o in close_tactic_name_ls:
            # 获取战法的类名
            sg_tactic = SgTactic.objects.get(name=close_tactic_o)
            tactic_class_name = sg_tactic.class_name
            # 实例化并关联bt_task
            cls = getattr(tactic, tactic_class_name)
            tactic_ins = cls()
            tactic_ins.bt_task = self
            # 加入相应容器
            self.close_tactic_cont.append(tactic_ins)
            pass

    def traverse(self):
        """遍历所属行情数据"""
        push_times = len(self.data) // push_process_num
        while self.idx < len(self.data):
            # self.idx每够1/10，推送一次数据到前端
            if self.idx % push_times == 0:
                self.queue.put(["process", self.idx_addend, self.idx, len(self.data)])
                # print(["process", self.idx_addend, self.idx, len(self.data)])
            if self.idx >= prepare_num - 1:
                # 执行战法
                self.execute_tactic()
            elif self.idx == 0:
                # 如果idx为0，则特殊调用一次
                self.execute_tactic()
            else:
                pass
            # idx自增
            self.idx += 1
        self.queue.put(self.return_order())
        # show_order_ls = []
        # for order_o in self.com_order_ls:
        #     show_order_ls.append([order_o.idx, order_o.remark])
        # print(show_order_ls)

    def order_fix(self, order, tactic_type: TacticType):
        """对传进来的订单进行完善"""
        # 读取strategy_direction
        strategy_direction = self.variable_value["strategy_direction"]
        # 为order加idx
        order.idx = self.idx
        if tactic_type in [TacticType.FILTER]:
            if order.order_size != 0:
                raise ValueError("过滤订单在创建时数量应该为0。")
            if order.order_dir is None:
                raise ValueError("战法类型为FILTER，但未指定具体的订单类型，逻辑错误。")
            elif order.order_dir in [OrderDir.REJECT, OrderDir.PASS]:
                order.order_size = 0
                return
            else:
                pass
        elif tactic_type in [TacticType.OPEN]:
            # order的order size必须为正
            if order.order_size <= 0:
                raise ValueError("开仓订单的订单数量在创建时不能为负数或0")
            if strategy_direction == 1:
                # 当前为做多方向且当前订单为开仓单，设置方向为BUY
                order.order_dir = OrderDir.BUY
            elif strategy_direction == -1:
                # 当前为做空方向且当前订单为开仓单，设置方向为SHORT
                order.order_dir = OrderDir.SHORT
                # 结合订单方向，修正order size，空为负，多为正
                order.order_size = -abs(order.order_size)
            # 若为其他策略方向报错
            else:
                raise ValueError("不支持的策略方向，逻辑错误")
        elif tactic_type in [TacticType.CLOSE]:
            # order的order size必须为自然数，为0表示全部平仓
            if order.order_size < 0:
                raise ValueError("平仓订单的订单数量在创建时不能为负数")
            if strategy_direction == 1:
                # 当前为做多方向且当前订单为平仓单，设置方向为SELL
                order.order_dir = OrderDir.SELL
                # 结合订单方向，修正order size，空为负，多为正
                order.order_size = -abs(order.order_size)
            elif strategy_direction == -1:
                # 当前为做空方向且当前订单为平仓单，设置方向为LONG
                order.order_dir = OrderDir.LONG
            # 若为其他策略方向报错
            else:
                raise ValueError("不支持的策略方向，逻辑错误")
        else:
            raise ValueError("错误的战法类型。")

    def execute_tactic(self):
        """执行战法，在此处理战法发出订单的逻辑"""
        # 1、最后一个idx必须发出拒单的订单（不需要了，最后一个即便是有仓位，也不会影响后续进程的仓位），
        # ！！！ 这种方法存在误差，由于idx为0时发出拒单，但idx这个行情存续期的仓位是来源于之前的进程（若有），故idx为0这个位置的pos数据无法准确设定，程序运行该误差存在
        # 2、对当前的pos_ls进行维护
        # 3、根据当前pos_ls的情况决定订单是否执行
        #  !! 本函数结束前务必执行更新仓位列表pos_ls的操作
        # 第一次遍历先发一笔拒单idx为0
        if self.idx == 0:
            first_order = Order(0, "首个行情拒单", OrderDir.REJECT, 0)
            self.com_order_ls.append(first_order)
            # 调整当前仓位信息
            self.update_now_pos(first_order)

            # 订单通过，计入com_order_ls
            self.com_order_ls.append(first_order)

            # 调整pos_ls信息
            self.update_pos_and_value_ls(self.idx, self.now_pos)
            return

        # 执行过滤战法，--------------------
        filter_order_ls = []
        for filter_tactic_ins in self.filter_tactic_cont:
            filter_tactic_ins.idx = self.idx
            order = filter_tactic_ins.execute()
            if order is None:
                pass
            else:
                filter_order_ls.append(order)

        filter_level = 0
        last_reject_order = None
        # 遍历所有过滤战法给出最终过滤策略
        for filter_order in filter_order_ls:
            if filter_order.order_dir == OrderDir.PASS:
                if filter_level < 1:
                    filter_level = 1
            elif filter_order.order_dir == OrderDir.REJECT:
                last_reject_order = filter_order
                if filter_level < 2:
                    filter_level = 2
            else:
                raise ValueError("过滤订单不应包含PASS、REJECT之外的订单方向")

        if filter_level == 0:
            # 不执行过滤
            pass
        elif filter_level == 1:
            # 执行一级过滤，在当前行情保持当前仓位状态
            # 调整pos_ls信息
            self.update_pos_and_value_ls(self.idx, self.now_pos)
            return
        elif filter_level == 2:
            # 执行二级过滤，发出拒单，清空仓位
            # 出现被过滤掉的情况，且订单类型为拒绝，需要将其加入订单列表来清空前面的仓位，中止本次遍历
            if len(self.com_order_ls) > 0 and self.com_order_ls[-1].order_dir != OrderDir.REJECT:
                if last_reject_order is None or last_reject_order.order_dir != OrderDir.REJECT:
                    raise ValueError("此处的last_reject_order不应为None或方向不等于REJECT，逻辑错误")
                self.order_fix(last_reject_order, TacticType.FILTER)
                # 调整仓位信息
                self.update_now_pos(last_reject_order)
                # 订单通过，计入com_order_ls
                self.com_order_ls.append(last_reject_order)

            # 调整pos_ls信息
            self.update_pos_and_value_ls(self.idx, self.now_pos)
            return
        # 过滤逻辑执行完毕 ------------------------

        # 执行开仓战法 -----------------------
        for open_tactic_ins in self.open_tactic_cont:
            open_tactic_ins.idx = self.idx
            order = open_tactic_ins.execute()
            if order is None:
                continue
            # 先对订单进行调整
            self.order_fix(order, TacticType.OPEN)
            order = self.judge_order(order)
            if order is not None:
                # 调整仓位信息
                self.update_now_pos(order)
                # 订单通过，计入com_order_ls
                self.com_order_ls.append(order)
            else:
                continue
        # 开仓逻辑执行完毕 -----------------------

        # 执行平仓战法 ------------------------
        for close_tactic_ins in self.close_tactic_cont:
            close_tactic_ins.idx = self.idx
            order = close_tactic_ins.execute()
            if order is None:
                continue
            # 先对订单进行调整
            self.order_fix(order, TacticType.CLOSE)
            order = self.judge_order(order)
            if order is not None:
                # 调整仓位信息
                self.update_now_pos(order)
                # 订单通过，计入com_order_ls
                self.com_order_ls.append(order)
            else:
                continue

        # 平仓逻辑执行完毕 -----------------------

        # 更新仓位列表，根据当前的idx确定在pos_ls中的位置
        self.update_pos_and_value_ls(self.idx, self.now_pos)

    def judge_order(self, order):
        if self.idx == 0:
            self.now_pos = 0
        else:
            self.now_pos = self.pos_ls[-1]  # 取pos_ls最后一个值作为当前的仓位
        # 根据当前pos_ls判断订单是否计入
        if order.order_dir in [OrderDir.BUY, OrderDir.SHORT]:
            if self.now_pos * order.order_size < 0:
                # 开仓单与原有仓位方向相反，则拒绝
                return None
            else:
                # 开仓单与原有仓位方向一致，但超过最大持仓限制，则拒绝
                if abs(self.now_pos + order.order_size) > max_pos:
                    return None
        elif order.order_dir in [OrderDir.SELL, OrderDir.LONG]:
            # 无仓位但发出平仓或行情拒绝，则拒绝
            if self.now_pos == 0:
                return None
            # 加入限制，不能使订单成交后直接导致仓位方向改变，若有则拒绝
            if (self.now_pos + order.order_size) * self.now_pos < 0:
                return None
        elif order.order_dir in [OrderDir.REJECT]:
            if self.now_pos != 0:
                # 订单方向为拒绝，但有仓位，则生成相应平仓单，令仓位归零
                if self.now_pos > 0:
                    order_o_f = Order(0, remark="行情被过滤，故全平", idx=order.idx)
                    order_o_f.order_dir = OrderDir.SELL
                else:
                    order_o_f = Order(0, remark="行情被过滤，故全平", idx=order.idx)
                    order_o_f.order_dir = OrderDir.LONG
                order = order_o_f
            else:
                return None
        else:
            return None

        return order

    def update_now_pos(self, order):
        # 整合订单列表，及时调整仓位信息
        if order.order_size == 0:
            self.now_pos = 0
        else:
            # 加入判断，由于order_size始终为自然数，方向为sell或short时，务必区分使得运算向仓位减少的方向，buy和long时，仓位向增加的方向
            self.now_pos = self.now_pos + order.order_size

    def update_pos_and_value_ls(self, idx, pos_val):
        """根据idx为pos_ls填充值，覆盖式填充，并计算value_ls的值"""
        # idx为当前行情的索引，实际受影响的仓位为下一个
        len_pos_ls = len(self.pos_ls)
        pos_ls_index = idx + 1  # 需在遍历完成后  将最后一个仓位数据删除
        if len_pos_ls == 0:
            # 若当前pos_ls为空
            self.pos_ls = [0] * (pos_ls_index + 1)
            self.pos_ls[-1] = pos_val
        else:
            # 若不为空
            if len_pos_ls >= pos_ls_index + 1:
                # pos_ls长度满足当前指定索引
                self.pos_ls[pos_ls_index] = pos_val
            else:
                # pos_ls长度不满足当前指定索引
                next_pos = self.pos_ls[-1]
                # 需要补的pos个数
                diff_pos_num = (pos_ls_index + 1) - len_pos_ls
                # 需要补的pos列表
                diff_pos_ls = [next_pos] * diff_pos_num
                # 组合一下
                self.pos_ls += diff_pos_ls
                # 赋值
                self.pos_ls[pos_ls_index] = pos_val
        # 计算需要提取的close数量
        new_pos_ls_len = len(self.pos_ls)
        # 提取close_ls
        part_close_ls = self.close[:new_pos_ls_len]
        # close变化
        diff_close = [0]
        for i in range(len(part_close_ls)):
            if i == 0:
                continue
            diff_close.append(part_close_ls[i] - part_close_ls[i - 1])
        # 将一阶差分与仓位进行点乘
        dot_mul = [a * b for a, b in zip(self.pos_ls, diff_close)]  # 获取了每个点对应的盈亏情况
        # 将盈亏情况进行累加获得净值序列
        total = 0
        cum_profit_ls = []
        for profit in dot_mul:
            total += profit
            cum_profit_ls.append(total)
        # 将其平移为初始值为100净值序列
        self.value_ls = [cum + ori_value_val for cum in cum_profit_ls]

    def return_order(self):
        """订单列表作为返回值"""
        return ["end", self.idx_addend, self.com_order_ls, self.pos_ls]


class SingleIntegrateProcess:
    def __init__(self):
        self.bt_ins = None
        self.now_pos = None
        self.max_pos = max_pos
        self.max_index = None
        # 全量过程数据记录
        self.com_datetime_ls = []
        self.com_close_ls = []
        self.com_value_ls = []
        self.com_pos_ls = []
        self.com_order_ls = []
        self.integrate_order_ls = []

        # 参数
        self.ori_value = 100

    def set_bt_ins(self, bt_ins):
        self.bt_ins = bt_ins
        self.max_index = len(self.bt_ins.data)

    def set_com_order_ls(self, order_ls):
        self.com_order_ls = order_ls

    def gen_datetime_ls(self):
        """为引擎添加com_datetime_ls的值"""
        self.com_datetime_ls = self.bt_ins.data.index.tolist()

    def gen_close_ls(self):
        """为引擎添加com_close_ls的值"""
        self.com_close_ls = self.bt_ins.data["close"].tolist()

    def gen_pos_ls(self):
        """将当前仓位信息返回"""
        self.com_pos_ls = self.bt_ins.pos_ls[:-1]

    def gen_pos_ls2(self):
        """根据全量订单列表数据生成仓位线变量，并生成整合订单列表"""

        def find_order(idx, com_order_ls):
            found_ls = []
            for order_oo in com_order_ls:
                if order_oo.idx == idx:
                    found_ls.append(order_oo)
            return found_ls

        # 需要整个行情的索引总数
        pos_ls = []
        for i in range(self.max_index):
            if i == 0:
                self.now_pos = 0  # 初始令0作为当前的仓位
            else:
                self.now_pos = pos_ls[-1]  # 取pos_ls最后一个值作为当前的仓位
            found_order_ls = find_order(i, self.com_order_ls)

            for order_o in found_order_ls:
                # 先判断订单是否满足条件，符合逻辑再保留下来
                if order_o.order_dir in [OrderDir.BUY, OrderDir.SHORT]:
                    if self.now_pos * order_o.order_size < 0:
                        # 开仓单与原有仓位方向相反，则拒绝
                        continue
                    else:
                        # 开仓单与原有仓位方向一致，但超过最大持仓限制，则拒绝
                        if abs(self.now_pos + order_o.order_size) > self.max_pos:
                            continue
                elif order_o.order_dir in [OrderDir.SELL, OrderDir.LONG]:
                    # 无仓位但发出平仓或行情拒绝，则拒绝
                    if self.now_pos == 0:
                        continue
                    # 加入限制，不能使订单成交后直接导致仓位方向改变，若有则拒绝
                    if (self.now_pos + order_o.order_size) * self.now_pos < 0:
                        continue
                elif order_o.order_dir in [OrderDir.REJECT]:
                    if self.now_pos != 0:
                        # 订单方向为拒绝，但有仓位，则生成相应平仓单，令仓位归零
                        if self.now_pos > 0:
                            order_o_f = Order(0, remark="行情被过滤，故全平", idx=order_o.idx)
                            order_o_f.order_dir = OrderDir.SELL
                        else:
                            order_o_f = Order(0, remark="行情被过滤，故全平", idx=order_o.idx)
                            order_o_f.order_dir = OrderDir.LONG
                        order_o = order_o_f
                    else:
                        continue
                else:
                    continue
                # 整合订单列表，及时调整仓位信息
                if order_o.order_size == 0:
                    self.now_pos = 0
                else:
                    # 加入判断，由于order_size始终为自然数，方向为sell或short时，务必区分使得运算向仓位减少的方向，buy和long时，仓位向增加的方向
                    self.now_pos = self.now_pos + order_o.order_size
                # 通过筛选，则加入整合订单列表，要确保加入整合订单列表的订单是恰好符合逻辑，天衣无缝的
                self.integrate_order_ls.append(order_o)

            pos_ls.append(self.now_pos)
        # 添加com_pos_ls的值
        self.com_pos_ls = [0] + pos_ls[:-1]  # 后移一位更合适，因为当天计算的仓位实际是下一日的仓位，应与下一日的涨幅相乘

    def gen_value_ls(self):
        """根据pos_ls和close_ls生成value_ls"""
        close_ls = self.com_close_ls
        pos_ls = self.com_pos_ls
        # 先计算close_ls的一阶差分
        close_diff = [0]
        for i in range(len(close_ls)):
            if i == 0:
                continue
            close_diff.append(close_ls[i] - close_ls[i - 1])
        # 将一阶差分与仓位进行点乘
        dot_mul = [a * b for a, b in zip(pos_ls, close_diff)]  # 获取了每个点对应的盈亏情况
        # 将盈亏情况进行累加获得净值序列
        total = 0
        cum_profit_ls = []
        for profit in dot_mul:
            total += profit
            cum_profit_ls.append(total)
        # 将其平移为初始值为100净值序列

        value_ls = [cum + self.ori_value for cum in cum_profit_ls]
        # 添加com_value_ls的值
        self.com_value_ls = value_ls

    def save_csv(self):
        """保存close、pos、value数据到本地csv"""
        save_data = {
            "close": self.com_close_ls,
            "pos": self.com_pos_ls,
            "value": self.com_value_ls
        }
        # 将字典转换为 DataFrame
        df = pd.DataFrame(save_data, index=self.com_datetime_ls)
        df.index.name = "datetime"
        # 获取当前时间
        current_time = datetime_.now()

        # 格式化为所需的文件名格式
        file_name = current_time.strftime("%m-%d_%H-%M_bt_task.csv")
        # 定义存储路径
        path = os.path.join(BASE_DIR, "data", "single", file_name)

        # 若文件夹不存在则创建
        folder_path = os.path.dirname(path)

        # 如果文件夹不存在，创建它
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
        # 将 DataFrame 保存为 CSV 文件
        safe_to_csv(df, path)

        print(f"回测的结果数据已存储至{path}")


class Fruit:
    def __init__(self, engine):
        """用于根据整合后的close、value、pos、com_order、integrate_order列表生成回测报告包括
        close_ls、value_ls、pos_ls、indicator_ls、describe_text"""
        self.engine = engine
        self.trade_profit_ls = []  # 交易收益列表
        self.trade_holding_ls = []  # 交易持仓时间列表
        pass

    def gen_part_node(self, num=300):
        """根据全量基础节点数据，生成部分节点数据"""
        # 获取com_close_ls、com_pos_ls、com_value_ls
        com_datetime_ls = self.engine.integrate_ins.com_datetime_ls
        com_close_ls = self.engine.integrate_ins.com_close_ls
        com_pos_ls = self.engine.integrate_ins.com_pos_ls
        com_value_ls = self.engine.integrate_ins.com_value_ls
        # 要求包含第一个和最后一个，均匀取点
        datetime_ls = get_sample(com_datetime_ls, num, type="lr")
        close_ls = get_sample(com_close_ls, num, type="lr")
        pos_ls = get_sample(com_pos_ls, num, type="lr")
        value_ls = get_sample(com_value_ls, num, type="lr")

        # 将part_node数据存放至variable_value中
        # 将datetime_ls中的元素统一变成json可处理的形式
        datetime_json_ls = []
        for tms in datetime_ls:
            datetime_json_ls.append(tms.strftime("%Y-%m-%d %H:%M:%S"))
        self.engine.variable_value["lab_var_part2_5"]["datetime_ls"] = datetime_json_ls
        self.engine.variable_value["lab_var_part2_5"]["close_ls"] = [round(x, 2) for x in close_ls]
        self.engine.variable_value["lab_var_part2_5"]["value_ls"] = [round(x, 2) for x in value_ls]
        self.engine.variable_value["lab_var_part2_5"]["pos_ls"] = [round(x, 2) for x in pos_ls]

    def gen_trade(self):
        """生成交易的列表"""
        com_pos_ls = self.engine.integrate_ins.com_pos_ls
        com_value_ls = self.engine.integrate_ins.com_value_ls
        # 交易的格式为一个数值，正负号代表盈亏
        holding = False  # 是否正在持仓
        open_value = None  # 开仓时的净值
        open_idx = None  # 开仓时的索引
        trade_ls = []
        holding_ls = []
        for i in range(len(com_pos_ls)):
            if i == 0:
                continue
            if com_pos_ls[i] != 0 and com_pos_ls[i - 1] == 0 and not holding:
                # 一笔交易的开始
                holding = True
                open_value = com_value_ls[i - 1]
                open_idx = i
            elif com_pos_ls[i] == 0 and com_pos_ls[i - 1] != 0 and holding:
                # 一笔交易的结束
                holding = False
                trade_ls.append(com_value_ls[i] - open_value)
                holding_ls.append(i - open_idx)
                open_value = None
                open_idx = None
            else:
                pass
        self.trade_profit_ls = trade_ls
        self.trade_holding_ls = holding_ls

    def gen_trade_chart(self):
        """获取交易图表数据，含交易收益列表和交易持仓列表"""
        # 对收益按从小到大排序，相应持仓数据同步调整顺序
        # 将 trade_profit_ls 和 trade_holding_ls 结合在一起
        combined = list(zip(self.trade_profit_ls, self.trade_holding_ls))
        # 按照 trade_profit_ls 中的值进行排序
        combined.sort(key=lambda x: x[0])

        # 分别提取排序后的 trade_profit_ls 和 trade_holding_ls
        self.trade_profit_ls, self.trade_holding_ls = zip(*combined)

        # 转换回列表，如果需要
        self.trade_profit_ls = list(self.trade_profit_ls)
        self.trade_holding_ls = list(self.trade_holding_ls)

        self.engine.variable_value["lab_var_part2_5"]["profit_ls"] = self.trade_profit_ls
        self.engine.variable_value["lab_var_part2_5"]["holding_ls"] = self.trade_holding_ls

    def gen_indicator(self):
        """根据全量基础节点数据、全量订单数据、整合订单数据生成回测指标"""
        # 获取com_close_ls、com_pos_ls、com_value_ls
        com_pos_arr = np.array(self.engine.integrate_ins.com_pos_ls)
        com_value_arr = np.array(self.engine.integrate_ins.com_value_ls)
        # 回测指标运算所需基本条件
        trade_ls = self.trade_profit_ls
        if len(trade_ls) == 0:
            print("无成交记录，退出")
            return
        # 总收益率
        total_return = com_value_arr[-1] / com_value_arr[0] - 1
        # 夏普比率
        daily_returns = (com_value_arr[1:] - com_value_arr[:-1]) / com_value_arr[:-1]
        sharpe_ratio = daily_returns.mean() * 252 / daily_returns.std()  # 假设无风险利率为0
        # 最大回撤
        rolling_max = np.maximum.accumulate(com_value_arr)
        daily_drawdown = com_value_arr / rolling_max - 1
        max_drawdown = daily_drawdown.min()

        # 订单分析（表格）
        # 累计交易数量
        total_trades = len(trade_ls)
        # 单笔交易平均占用行情节点个数
        average_daily_orders = len(com_pos_arr[com_pos_arr != 0]) / total_trades
        # 交易分析（表格+频率直方图）
        trade_arr = np.array(trade_ls)
        # 累计交易数量、单笔交易盈利、盈亏比、交易时长频率分布直方图
        # 单笔交易利润率
        average_trade_profit = trade_arr.mean() / ori_value_val
        # 盈亏比
        average_win = trade_arr[trade_arr >= 0].sum()
        win_num = sum(trade_arr >= 0)
        average_loss = trade_arr[trade_arr < 0].sum()
        loss_num = sum(trade_arr < 0)

        profit_loss_ratio = average_win / abs(average_loss)
        # 交易胜率
        win_rate = win_num / (win_num + loss_num)

        indicator_ls = [
            ["总收益率", float(round(total_return, 4)), IndicatorDescribe["总收益率"]],
            ["夏普比率", float(round(sharpe_ratio, 4)), IndicatorDescribe["夏普比率"]],
            ["最大回撤", float(round(-max_drawdown, 4)), IndicatorDescribe["最大回撤"]],
            ["平均持仓", float(round(average_daily_orders, 0)), IndicatorDescribe["平均持仓"]],
            ["交易数量", float(round(total_trades, 4)), IndicatorDescribe["交易数量"]],
            ["单笔利润率", float(round(average_trade_profit, 4)), IndicatorDescribe["单笔利润率"]],
            ["盈利交易数量", float(round(win_num, 4)), IndicatorDescribe["盈利交易数量"]],
            ["亏损交易数量", float(round(loss_num, 4)), IndicatorDescribe["亏损交易数量"]],
            ["累计盈利", float(round(average_win, 4)), IndicatorDescribe["累计盈利"]],
            ["累计亏损", float(round(average_loss, 4)), IndicatorDescribe["累计亏损"]],
            ["盈亏比", float(round(profit_loss_ratio, 4)), IndicatorDescribe["盈亏比"]],
            ["交易胜率", float(round(win_rate, 4)), IndicatorDescribe["交易胜率"]],
        ]
        self.engine.variable_value["lab_var_part2_5"]["indicator_ls"] = indicator_ls

    def gen_describe_text(self):
        """根据回测指标及其他生成回测报告的描述文本"""
        indicator_ls = self.engine.variable_value["lab_var_part2_5"]["indicator_ls"]
        if len(indicator_ls) == 0:
            print("无指标数据，退出")
            return
        total_return = indicator_ls[0][1]
        sharpe_ratio = indicator_ls[1][1]
        max_drawdown = indicator_ls[2][1]
        average_daily_orders = indicator_ls[3][1]
        average_trade_profit = indicator_ls[5][1]
        profit_loss_ratio = indicator_ls[10][1]
        win_rate = indicator_ls[11][1]

        total_return_text = f"总收益率为：{self.percentage(total_return)}。"
        sharpe_ratio_text = f"夏普比率为：{float(round(sharpe_ratio, 2))}，值越高代表单位风险带来的回报越高，请在相同规格的数据下进行对比。"
        max_drawdown_text = f"最大回撤为：{self.percentage(max_drawdown)}，代表本次回测的最大风险。"
        average_daily_orders_text = f"平均持仓时间为：{average_daily_orders}。"
        average_trade_profit_text = f"平均单笔交易盈利为：{self.percentage(average_trade_profit, 4)}。"
        profit_loss_ratio_text = f"盈亏比为：{profit_loss_ratio}。"
        win_rate_text = f"交易胜率为：{self.percentage(win_rate)}。"

        text = (f"DDQuant-X为您提取本次实验的关键性指标。" + total_return_text + sharpe_ratio_text + max_drawdown_text +
                average_daily_orders_text + average_trade_profit_text + profit_loss_ratio_text + win_rate_text)
        self.engine.variable_value["lab_var_part2_5"]["describe_text"] = text

    @staticmethod
    def save_var_json():
        """将先前生成的var数据保存"""
        save_var_json()

    @staticmethod
    def percentage(number, places=2):
        return str(round(number * 100, places)) + '%'
